﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Objects;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Infrastructure.Data.Core
{
    public static class QueryableExtensions
    {
        public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> queryable, string path)
           where TEntity : class
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("IncludePathCannotBeNullOrEmpty");
            var query = queryable as DbSet<TEntity>;

            if (query != null)//if is a EF ObjectQuery object
                return query.Include(path);
            return null;
        }

        /// <summary>
        /// Include extension method for IQueryable
        /// </summary>
        /// <typeparam name="TEntity">Type of elements in IQueryable</typeparam>
        /// <param name="queryable">Queryable object</param>
        /// <param name="path">Expression with path to include</param>
        /// <returns>Queryable object with include path information</returns>
        public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> queryable, Expression<Func<TEntity, object>> path)
            where TEntity : class
        {
            return Include<TEntity>(queryable, AnalyzeExpressionPath(path));
        }

        public static IEnumerable<TEntity> Include<TEntity>(this IEnumerable<TEntity> queryable, string path)
           where TEntity : class
        {
            if (String.IsNullOrEmpty(path))
                throw new ArgumentNullException("IncludePathCannotBeNullOrEmpty");

            var query = queryable as DbSet<TEntity>;

            if (query != null)//if is a EF ObjectQuery object
                return query.Include(path);
            return null;
        }

        /// <summary>
        /// Include extension method for IQueryable
        /// </summary>
        /// <typeparam name="TEntity">Type of elements in IQueryable</typeparam>
        /// <param name="queryable">Queryable object</param>
        /// <param name="path">Expression with path to include</param>
        /// <returns>Queryable object with include path information</returns>
        public static IEnumerable<TEntity> Include<TEntity>(this IEnumerable<TEntity> queryable, Expression<Func<TEntity, object>> path)
            where TEntity : class
        {
            return Include<TEntity>(queryable, AnalyzeExpressionPath(path));
        }


        static string AnalyzeExpressionPath<TEntity, TS>(Expression<Func<TEntity, TS>> expression)
           where TEntity : class
        {
            if (expression == (Expression<Func<TEntity, TS>>)null)
                throw new ArgumentNullException("ExpressionPathNotValid");

            var body = expression.Body as MemberExpression;
            if (
                    (
                    (body == null)
                    ||
                    !body.Member.DeclaringType.IsAssignableFrom(typeof(TEntity))
                    )
                    ||
                    (body.Expression.NodeType != ExpressionType.Parameter))
            {
                throw new ArgumentException("ExpressionPathNotValid");
            }
            else
                return body.Member.Name;
        }
    }
}
